package com.specter.mvc.model.batis;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;

import com.specter.mvc.model.Page;
import com.specter.mvc.model.Request;
import com.specter.mvc.model.ResultMap;
import com.specter.mvc.model.batis.plugin.PaginationInterceptor;
import com.specter.utils.FreemarkerUtils;

import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.MappedSuperclass;
import lombok.extern.slf4j.Slf4j;

/**
 * Note:继承并重新设置sqlSessionFactory，完成自动的配置文件生成并加载
 * 
 * @author Liang.Wang
 * @version Mar 1, 2017
 */
@Slf4j
public class MybatisSqlSessionFactoryBean extends SqlSessionFactoryBean {

	// 参考spring.orm.hibernate的JPA注解方法
	private String[] packagesToScan;

	private static final TypeFilter[] DEFAULT_ENTITY_TYPE_FILTERS = new TypeFilter[] {
			new AnnotationTypeFilter(Entity.class, false),
			new AnnotationTypeFilter(Embeddable.class, false),
			new AnnotationTypeFilter(MappedSuperclass.class, false)
	};

	// 自动设置数据源
	@Autowired
	@Override
	public void setDataSource(DataSource dataSource) {
		super.setDataSource(dataSource);
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		// Resource[] ms = (new
		// PathMatchingResourcePatternResolver()).getResources("classpath*:**/MybatisGeneric.mapper.xml");
		super.setTypeAliases(Page.class, Request.class, ResultMap.class);// 先 注册常用类型，默认为类简写的小写
		if (getDatabaseIdProvider() == null) {// 数据库方言类型
			setDatabaseIdProvider(new VendorDatabaseIdProvider());
		}
		Configuration config = super.buildSqlSessionFactory().getConfiguration();
		config.addInterceptor(new PaginationInterceptor(config.getDatabaseId()));

		setPrivateFiled("sqlSessionFactory", buildAutoMappedSessionFactory(config));
	}

	protected SqlSessionFactory buildAutoMappedSessionFactory(Configuration config) throws Exception {
		// 自动装配配置文件开始

		// 1、搜索所有的Entity注解实体
		this.scanPackages(packagesToScan);
		Map<String, JPAEntityRegistry.JPAEntity> registry = JPAEntityRegistry.get();
		for (Map.Entry<String, JPAEntityRegistry.JPAEntity> entry : registry.entrySet()) {
			// 2、将注解实体封装到特定的对象中
			JPAEntityRegistry.JPAEntity e = entry.getValue();
			// 3、将对象的值和模板进行解析生成映射文件
			String mapper = FreemarkerUtils.render("com/specter/orm/ibatis/MybatisTemplate.mapper.ftl", e);
			// System.out.println(mapper);
			// 4、映射文件加载到内存中可供直接使用
			(new XMLMapperBuilder(in(mapper), config, (e.getClassName() + "Auto_.mapper.xml"), config.getSqlFragments())).parse();// 内部机制会判断是否重复
			if (log.isDebugEnabled()) {
				log.debug("完成映射实体：{}", e.getClassName());
			}
		}

		// 自动装配配置文件结束
		log.info("------ 主数据库类型：{} 自动扫描JPA注解实体类并装配成mybatis映射文件! 共：{} 个 ! ------", config.getDatabaseId(), registry.size());
		SqlSessionFactoryBuilder sqlSessionFactoryBuilder = (SqlSessionFactoryBuilder) getPrivateFiled("sqlSessionFactoryBuilder");
		return sqlSessionFactoryBuilder.build(config);
	}

	/**
	 * 访问上级类的私有变量
	 * 
	 * @param fieldName 变量名
	 * @return 变量值
	 */
	protected Object getPrivateFiled(String fieldName) {
		Object obj = null;
		try {
			Field field = SqlSessionFactoryBean.class.getDeclaredField(fieldName);
			field.setAccessible(true);
			obj = field.get(this);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return obj;
	}

	/**
	 * 给上级类私有变量赋值
	 * 
	 * @param fieldName  变量名
	 * @param fieldValue 变量值
	 */
	protected void setPrivateFiled(String fieldName, Object fieldValue) {
		try {
			Field field = SqlSessionFactoryBean.class.getDeclaredField(fieldName);
			field.setAccessible(true);
			field.set(this, fieldValue);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 转化字符串为输入流
	 * 
	 * @param input 输入字符串
	 * @return 输入流
	 */
	private InputStream in(String input) {
		InputStream in = null;
		try {
			in = new ByteArrayInputStream(input.getBytes("UTF-8"));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return in;
	}

	public void setPackagesToScan(String... packagesToScan) {
		this.packagesToScan = packagesToScan;
	}

	/**
	 * 扫描所有指定包下的JPA类
	 * 
	 * @param packagesToScan
	 * @return
	 */
	public void scanPackages(String... packagesToScan) {
		if (packagesToScan == null || packagesToScan.length < 1) {
			return;
		}
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		try {
			for (String pkg : packagesToScan) {
				String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(pkg) + "/**/*.class";
				Resource[] resources = resourcePatternResolver.getResources(pattern);
				MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

				for (Resource resource : resources) {
					if (resource.isReadable()) {
						MetadataReader reader = readerFactory.getMetadataReader(resource);
						String className = reader.getClassMetadata().getClassName();

						for (TypeFilter filter : DEFAULT_ENTITY_TYPE_FILTERS) {// 过滤某一注解类
							if (filter.match(reader, readerFactory)) {
								Class<?> clazz = resourcePatternResolver.getClassLoader().loadClass(className);
								JPAEntityRegistry.add(clazz);
							}
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
